package com.fount4j.generator.introspect;

import com.fount4j.generator.context.GeneratorContext;
import com.fount4j.generator.exception.GenerateException;
import com.fount4j.generator.info.ColumnInfo;
import com.google.common.base.CaseFormat;
import com.google.common.collect.ImmutableMap;

import java.math.BigDecimal;
import java.sql.Types;
import java.util.Date;
import java.util.Map;

/**
 * 列解析器
 *
 * @author Morven 2016/12/22
 */
public class ColumnIntrospector {
    /**
     * 解析列
     *
     * @param context    生成器上下文
     * @param columnInfo 要解析的列
     */
    public void introspectColumn(GeneratorContext context, ColumnInfo columnInfo) {
        introspectPropertyName(context, columnInfo);
        introspectJdbcType(context, columnInfo);
        introspectLenScale(context, columnInfo);
        introspectNotEmptyExp(context, columnInfo);
    }

    /**
     * 列不为空的判断表达式
     *
     * @param context    生成器上下文
     * @param columnInfo 要解析的列
     */
    protected void introspectNotEmptyExp(GeneratorContext context, ColumnInfo columnInfo) {
        if (String.class.equals(columnInfo.getJavaType())) {
            columnInfo.setNotEmptyExp(" and " + columnInfo.getJavaName() + " != ''");
        }
    }

    /**
     * 解析列名
     *
     * @param context    生成器上下文
     * @param columnInfo 要解析的列
     */
    protected void introspectPropertyName(GeneratorContext context, ColumnInfo columnInfo) {
        String propertyName = CaseFormat.UPPER_UNDERSCORE
                .to(CaseFormat.LOWER_CAMEL, columnInfo.getRealName().toUpperCase());
        String propertyUpper = CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_CAMEL, propertyName);
        columnInfo.setJavaName(propertyName);
        columnInfo.setJavaNameUpper(propertyUpper);
    }

    /**
     * 解析字段的长度和精确度的描述
     *
     * @param context    生成器上下文
     * @param columnInfo 要解析的列
     */
    protected void introspectLenScale(GeneratorContext context, ColumnInfo columnInfo) {
        StringBuilder sb = new StringBuilder();
        if (columnInfo.getLen() > 0) {
            sb.append("(").append(columnInfo.getLen());
        } else if (columnInfo.getScale() > 0) {
            sb.append("(0");
        }
        if (columnInfo.getScale() > 0) {
            sb.append(",").append(columnInfo.getScale());
        }
        if (sb.length() > 0) {
            sb.append(")");
        }
        columnInfo.setLenScale(sb.toString());
    }

    /** 数据库列的类型与 MyBatis jdbcType 和实体类属性类型的对应关系 */
    private Map<Integer, JdbcTypeInformation> typeMap = ImmutableMap.<Integer, JdbcTypeInformation>builder()
            .put(Types.ARRAY, new JdbcTypeInformation("ARRAY", Object.class))
            .put(Types.BIGINT, new JdbcTypeInformation("BIGINT", Long.class))
            .put(Types.BINARY, new JdbcTypeInformation("BINARY", byte[].class))
            .put(Types.BIT, new JdbcTypeInformation("BIT", Boolean.class))
            .put(Types.BLOB, new JdbcTypeInformation("BLOB", byte[].class))
            .put(Types.BOOLEAN, new JdbcTypeInformation("BOOLEAN", Boolean.class))
            .put(Types.CHAR, new JdbcTypeInformation("CHAR", String.class))
            .put(Types.CLOB, new JdbcTypeInformation("CLOB", String.class))
            .put(Types.DATALINK, new JdbcTypeInformation("DATALINK", Object.class))
            .put(Types.DATE, new JdbcTypeInformation("DATE", Date.class))
            .put(Types.DECIMAL, new JdbcTypeInformation("DECIMAL", BigDecimal.class))
            .put(Types.DISTINCT, new JdbcTypeInformation("DISTINCT", Object.class))
            .put(Types.DOUBLE, new JdbcTypeInformation("DOUBLE", Double.class))
            .put(Types.FLOAT, new JdbcTypeInformation("FLOAT", Double.class))
            .put(Types.INTEGER, new JdbcTypeInformation("INTEGER", Integer.class))
            .put(Types.JAVA_OBJECT, new JdbcTypeInformation("JAVA_OBJECT", Object.class))
            .put(Types.LONGNVARCHAR, new JdbcTypeInformation("LONGNVARCHAR", String.class))
            .put(Types.LONGVARBINARY, new JdbcTypeInformation("LONGVARBINARY", byte[].class))
            .put(Types.LONGVARCHAR, new JdbcTypeInformation("LONGVARCHAR", String.class))
            .put(Types.NCHAR, new JdbcTypeInformation("NCHAR", String.class))
            .put(Types.NCLOB, new JdbcTypeInformation("NCLOB", String.class))
            .put(Types.NVARCHAR, new JdbcTypeInformation("NVARCHAR", String.class))
            .put(Types.NULL, new JdbcTypeInformation("NULL", Object.class))
            .put(Types.NUMERIC, new JdbcTypeInformation("NUMERIC", BigDecimal.class))
            .put(Types.OTHER, new JdbcTypeInformation("OTHER", Object.class))
            .put(Types.REAL, new JdbcTypeInformation("REAL", Float.class))
            .put(Types.REF, new JdbcTypeInformation("REF", Object.class))
            .put(Types.SMALLINT, new JdbcTypeInformation("SMALLINT", Short.class))
            .put(Types.STRUCT, new JdbcTypeInformation("STRUCT", Object.class))
            .put(Types.TIME, new JdbcTypeInformation("TIME", Date.class))
            .put(Types.TIMESTAMP, new JdbcTypeInformation("TIMESTAMP", Date.class))
            .put(Types.TINYINT, new JdbcTypeInformation("TINYINT", Byte.class))
            .put(Types.VARBINARY, new JdbcTypeInformation("VARBINARY", byte[].class))
            .put(Types.VARCHAR, new JdbcTypeInformation("VARCHAR", String.class)).build();

    /**
     * 解析列的类型
     *
     * @param context    生成器上下文
     * @param columnInfo 要解析的列
     */
    protected void introspectJdbcType(GeneratorContext context, ColumnInfo columnInfo) {
        JdbcTypeInformation jdbcTypeInformation = typeMap.get(columnInfo.getDbType());
        if (jdbcTypeInformation == null) {
            throw new GenerateException("未知的列类型: " + columnInfo.getDbTypeName());
        }
        columnInfo.setJdbcType(jdbcTypeInformation.getJdbcType());
        columnInfo.setJavaType(jdbcTypeInformation.getJavaType());
        if (jdbcTypeInformation.getJavaType().equals(byte[].class)) {
            columnInfo.setJavaTypeName("byte[]");
        } else {
            columnInfo.setJavaTypeName(jdbcTypeInformation.getJavaType().getSimpleName());
        }
    }
}
